package com.starblink.android.basic.network.base

import android.app.Activity
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.ApolloResponse
import com.apollographql.apollo3.api.Mutation
import com.apollographql.apollo3.api.Query
import com.apollographql.apollo3.cache.normalized.FetchPolicy
import com.apollographql.apollo3.cache.normalized.apolloStore
import com.apollographql.apollo3.cache.normalized.fetchPolicy
import com.apollographql.apollo3.cache.normalized.normalizedCache
import com.apollographql.apollo3.cache.normalized.sql.SqlNormalizedCacheFactory
import com.apollographql.apollo3.exception.ApolloCompositeException
import com.apollographql.apollo3.exception.ApolloException
import com.apollographql.apollo3.exception.ApolloHttpException
import com.apollographql.apollo3.exception.ApolloNetworkException
import com.apollographql.apollo3.exception.CacheMissException
import com.apollographql.apollo3.network.http.HttpNetworkTransport
import com.apollographql.apollo3.network.okHttpClient
import com.starblink.android.basic.BuildConfig
import com.starblink.android.basic.ext.checkNet
import com.starblink.android.basic.ext.string
import com.starblink.android.basic.ext.toastUI
import com.starblink.android.basic.network.config.HeadsHandle
import com.starblink.android.basic.network.interceptor.ApolloHttpInterceptor
import com.starblink.android.basic.network.interceptor.CountryApolloInterceptor
import com.starblink.android.basic.network.interceptor.ErrorApolloInterceptor
import com.starblink.android.basic.network.interceptor.SwitchSiteErrorApolloInterceptor
import com.starblink.android.basic.user.UserDataCenter
import com.starblink.android.basic.widget.LoadingDialogManager
import com.starblink.android.charles.MockHttpInterceptor
import com.starblink.android.charles.PacketCaptureInterceptor
import com.starblink.basic.apollo.network.SkApolloRequestComposer
import com.starblink.basic.storage.SkStorage
import com.starblink.basic.style.R
import com.starblink.basic.util.ActivityStackManage
import com.starblink.basic.util.log.YYLogUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import java.util.concurrent.TimeUnit
import kotlin.coroutines.cancellation.CancellationException


object SkGqlClient {

    private var apolloClient: ApolloClient? = null
    private var apolloNoToastClient: ApolloClient? = null

    private val sqlNormalizedCacheFactory = SqlNormalizedCacheFactory("apollo.db")

    private val okHttpClient = OkHttpClient.Builder()
        .callTimeout(10, TimeUnit.SECONDS)
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .retryOnConnectionFailure(true)
        .followRedirects(false)
        .addInterceptor(PacketCaptureInterceptor())
        .addInterceptor(MockHttpInterceptor())
        .addInterceptor(HttpLoggingInterceptor().apply {
            setLevel(if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE)
        })
        .build()

    //https://www.apollographql.com/docs/kotlin/caching/store
    open fun resetClient() {
        SkStorage.put(BaseSiteSwitch.START_UP_SITE_FIRST, false)
        HeadsHandle.getHttpDfHeads(true)
        //清除缓存
        // clearAll需要开启子线程，避免主线程竞争锁等待导致anr
        val tmpClient = apolloClient
        runBlocking(Dispatchers.Default) {
            tmpClient?.apolloStore?.clearAll()
        }
        //重置请求头
        apolloClient = null

        val tmpNoToastClient = apolloNoToastClient
        runBlocking(Dispatchers.Default) {
            tmpNoToastClient?.apolloStore?.clearAll()
        }
        //重置请求头
        apolloNoToastClient = null

        //重置所有缓存配置
        UserDataCenter.resetQueryCache()
    }


    private fun getClient(): ApolloClient {
        if (apolloClient == null) {
            val url = BaseApiClient.getCurrSvrCfgEnvUrl()
            apolloClient = ApolloClient.Builder()
                //请求URL
                //.serverUrl(url)
                //添加公共请求头
                .httpHeaders(HeadsHandle.getHttpDfHeads(false))
                //设置缓存 两种标准策略  内存缓存/sqlite缓存 当前使用为sqlite缓存
//                .normalizedCache(MemoryCacheFactory(maxSizeBytes = 10 * 1024 * 1024))
                .normalizedCache(sqlNormalizedCacheFactory)
                //拦截401
//                .addHttpInterceptor(AuthorizationInterceptor())
                //日志拦截，使用okhttpclient日志模块
                //.addInterceptor(LoggingApolloInterceptor(url))
//                .addInterceptor(PacketCaptureSubstituteApolloInterceptor(url))
                //错误拦截
                .addInterceptors(
                    listOf(
                        //处理站点切换
                        SwitchSiteErrorApolloInterceptor(),
                        //处理异常
                        ErrorApolloInterceptor(),
                        //拦截处理国家
                        CountryApolloInterceptor()
                    )
                )
                //.addHttpInterceptor(ApolloHttpInterceptor())
                .networkTransport(
                    HttpNetworkTransport.Builder()
                        //.serverUrl(url)
                        .addInterceptor(ApolloHttpInterceptor())
                        .okHttpClient(okHttpClient)
                        .httpRequestComposer(
                            SkApolloRequestComposer(url)
                        ).build()
                )
                //.okHttpClient(okHttpClient)
                .build()
        }

        return apolloClient!!

    }


    private fun getNoToastClient(): ApolloClient {
        if (apolloNoToastClient == null) {
            val url = BaseApiClient.getCurrSvrCfgEnvUrl()
            apolloNoToastClient = ApolloClient.Builder()
                //请求URL
                //.serverUrl(url)
                //添加公共请求头
                .httpHeaders(HeadsHandle.getHttpDfHeads(false))
                //设置缓存 两种标准策略  内存缓存/sqlite缓存 当前使用为sqlite缓存
//                .normalizedCache(MemoryCacheFactory(maxSizeBytes = 10 * 1024 * 1024))
                .normalizedCache(sqlNormalizedCacheFactory)
                //错误拦截
                .addInterceptors(
                    listOf(
                        //处理站点切换
                        SwitchSiteErrorApolloInterceptor(),
                        //拦截处理国家
                        CountryApolloInterceptor()
                    )
                )
                .networkTransport(
                    HttpNetworkTransport.Builder()
                        //.serverUrl(url)
                        .addInterceptor(ApolloHttpInterceptor())
                        .okHttpClient(okHttpClient)
                        .httpRequestComposer(
                            SkApolloRequestComposer(url)
                        ).build()
                )
                //.okHttpClient(okHttpClient)
                .build()
        }

        return apolloNoToastClient!!

    }


    suspend fun <D : Mutation.Data> skMutation(
        mutation: Mutation<D>,
        showFailToast: Boolean = true,
        showErrorToast: Boolean = true,
        //是否弹出默认loading
        showDefaultLoading: Boolean = false,
        ifNeedHiddenLoading: Boolean = true, //接口串行 控制loading显示
        //mutation请求默认总是使用网络！！！
        //mutation请求默认总是使用网络！！！
        //mutation请求默认总是使用网络！！！
        fetchPolicy: FetchPolicy = FetchPolicy.NetworkOnly,
        activity: Activity? = null
    ): ApolloResponse<D>? {
        defaultLoadingShow(showDefaultLoading, activity)
        try {
            val apolloClient = if (showFailToast) getClient() else getNoToastClient()
            val response = apolloClient
                .mutation(mutation)
                .fetchPolicy(fetchPolicy)
                .execute()
            if (ifNeedHiddenLoading) {
                defaultLoadingDismiss(showDefaultLoading)
            }
            return response
//            return getClient()
//                .mutation(mutation)
//                .fetchPolicy(fetchPolicy)
//                .execute()

        } catch (exception: Exception) {
            defaultLoadingDismiss(showDefaultLoading)
            if (BuildConfig.DEBUG) {
                exception.printStackTrace()
            }
            checkNet({
                when (exception) {
                    is ApolloCompositeException -> {
                        exception.suppressedExceptions.forEach { suppressed ->
                            handlerApolloException(suppressed as ApolloException)
                        }
                    }

                    else -> uploadErrorMsg(tag = "skMutation ${mutation.name()}", exception.message)
                }
                if (showErrorToast && exception !is CancellationException) {
                    //线上环境统一抛出服务异常
                    if (BaseApiClient.isPub()) {
                        YYLogUtils.e("---------------->  ${string(R.string.server_error)}")
//                        toastUI(CommUtils.getString(com.starblink.basic.style.R.string.server_error))
                    }
                }
            })
            YYLogUtils.e("${exception.message}")
            return null
        }

    }

    suspend fun <D : Query.Data> skQuery(
        query: Query<D>,
        showFailToast: Boolean = true,
        showErrorToast: Boolean = true,
        showDefaultLoading: Boolean = false,
        ifNeedHiddenLoading: Boolean = true, //接口串行 控制loading显示
        fetchPolicy: FetchPolicy = FetchPolicy.NetworkOnly,
        activity: Activity? = null
    ): ApolloResponse<D>? {
        defaultLoadingShow(showDefaultLoading, activity)
        try {
            val apolloClient = if (showFailToast) getClient() else getNoToastClient()
            val response = apolloClient.query(query)
                .fetchPolicy(fetchPolicy)
                .execute()
            if (ifNeedHiddenLoading) {
                defaultLoadingDismiss(showDefaultLoading)
            }
            return response

//            return getClient().query(query)
//                .fetchPolicy(fetchPolicy)
//                .execute()

        } catch (exception: Exception) {
            defaultLoadingDismiss(showDefaultLoading)
            if (BuildConfig.DEBUG) {
                exception.printStackTrace()
            }
            YYLogUtils.e("${exception.message}")
            checkNet({
                when (exception) {
                    is ApolloCompositeException -> {
                        exception.suppressedExceptions.forEach { suppressed ->
                            handlerApolloException(suppressed as ApolloException)
                        }
                    }

                    is CancellationException -> {
                        YYLogUtils.e("取消网络请求：$query")
                    }

                    else -> uploadErrorMsg(tag = "skQuery ${query.name()}",exception.message)
                }
                if (showErrorToast && exception !is CancellationException) {
                    //线上环境统一抛出服务异常
                    if (BaseApiClient.isPub()) {
                        YYLogUtils.e("---------------->  ${string(com.starblink.basic.style.R.string.server_error)}")
//                        toastUI(CommUtils.getString(com.starblink.basic.style.R.string.server_error))
                    }
                }
            })
            return null
        }
    }

    private fun handlerApolloException(e: ApolloException) {
        when (e) {
            is CacheMissException -> {
                //缓存未命中
                if (!BaseApiClient.isPub() && BuildConfig.DEBUG) {
                    toastUI("CacheMissException 1 ${e.message}")
                }
                resetClient()
            }// There was a cache miss
            is ApolloHttpException -> {
                if (!BaseApiClient.isPub() && BuildConfig.DEBUG) {
                    toastUI("ApolloHttpException ${e.message}")
                }
            }// The response was received with an HTTP error
            is ApolloNetworkException -> {
                if (!BaseApiClient.isPub() && BuildConfig.DEBUG) {
                    toastUI("ApolloNetworkException ${e.message}")
                }
            } // The response was not received due to a network exception
            else -> {
                if (!BaseApiClient.isPub() && BuildConfig.DEBUG) {
                    toastUI("ApolloException :${e.message}")
                }
            }// Something else happened (parsing error, ...)
        }
    }

    fun uploadErrorMsg(tag: String, eMsg: String?) {
        if ("Job was cancelled" == eMsg) {
            YYLogUtils.e("$tag -> Job was cancelled ----> $eMsg")
            return
        }
        if (!BaseApiClient.isPub() && BuildConfig.DEBUG) {
            toastUI("$tag -> $eMsg")
        }
        //flutter上报是个空方法！！！
        //flutter上报是个空方法！！！
        //flutter上报是个空方法！！！
        YYLogUtils.e("开始上报网络错误数据 $eMsg")
    }

    fun defaultLoadingShow(show: Boolean, activity: Activity? = null) {
        if (show) {
            MainScope().launch {
                LoadingDialogManager.get()
                    .showLoading(activity ?: ActivityStackManage.currentActivity())
            }
        }
    }

    fun defaultLoadingDismiss(show: Boolean) {
        if (show) {
            MainScope().launch {
                LoadingDialogManager.get().dismissLoading()
            }
        }
    }


}